iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 4
0
Modern Web

Go into Web!系列 第 4

Day4 | 無痛使用 Golang 打造屬於自己的網頁

  • 分享至 

  • xImage
  •  

前兩天已經介紹過基本的 Web 知識與安裝好相關環境了,接下來我們就使用 golang 原生提供的 net/http package 來建立一個簡單的網頁吧!

目標

我們預計建立一個簡單的網站,網站要符合以下幾個條件

  1. 存取 url 為 http://127.0.0.1:8888
  2. path 為 / 或是 /index 的時候存取首頁
  3. 首頁必須要是 html 格式

目標設定好就讓我們開始吧!

建立第一支網頁程式

透過 golang 建立網頁程式一點都不能,就讓我們一步一步的進行吧!

import 相關 package

在最一開始,我們 import 兩個 package 分別為 lognet/http

import (
	"log"
	"net/http"
)
  • log 就是用來輸出程式目前執行狀態的,log 的輸出可以分成不同的等級,這個我們後續會特別抽出來談
  • net/http 就是用來將網頁運行起來的關鍵,包含伺服器監聽與運行的方法與 request handler 都使用此 package 來實作

建立一個基本的 request handler

test 這個方法中我們可以看到有兩個參數分別為 http.ResponseWriterhttp.Request,這兩個參數分別對應到我們昨天所提到的 responserequest

func test(w http.ResponseWriter, r *http.Request) {
	w.WriteHeader(http.StatusOK)
	w.Write([]byte(`my first website`))
}

這個範例中,我們在方法中寫了兩行

  • 第一行就是在 header 寫入了 http.StatusOK 這個值,這個數值也是 net/http 這個 package 所提供的,他其實就是把 status code 給封裝起來,讓我們比較方便呼叫,因此,http.StatusOK 就是等於 200
  • 第二行就是寫入 response 的內容,回傳的型態是 []byte,因此我們寫入 []byte(`my first website`)

加入 routing

routing 的部分在此範例中我們寫在 main 方法中,範例如下

http.HandleFunc("/", test)

我們實作了 net/http package 中的 HandleFunc 方法,將上一部所寫的 test 方法與 / 這個 routing 進行綁定,讓 server 知道當進來的 traffic 的 routing 為 / 時要執行 test 方法

開始運行伺服器

直接實作 net/http 中提供的 ListenAndServe 方法,一樣是寫在 main 方法中

err := http.ListenAndServe(":8888", nil)
if err != nil {
    log.Fatal("ListenAndServe: ", err)
}

ListenAndServe 有兩個參數分別為 addresshandler

  • address 為存取的 urlport,因為沒有指定 url 所以沒有填寫,只單純寫 port
  • 本範例中沒有實作 handler,因此將它填寫為 nil

綜合上述步驟程式碼最終會像是這樣

package main

import (
	"log"
	"net/http"
)

func test(w http.ResponseWriter, r *http.Request) {
	w.WriteHeader(http.StatusOK)
	w.Write([]byte(`my first website`))
}

func main() {
	http.HandleFunc("/", test)
	err := http.ListenAndServe(":8888", nil)
	if err != nil {
		log.Fatal("ListenAndServe: ", err)
	}
}

將程式碼儲存成 main.go後,在 command line 中透過 go run main.go 指令將程式運行起來後,會看到以下畫面,程式會進入一個無窮迴圈不會停止,這樣就對了!

之後在瀏覽器輸入 http://127.0.0.1:8888 看到以下畫面就代表我們第一支網頁程式就完成拉!

你心裡一定想說「什麼?!就這麼簡單嗎?今天就這麼結束了嗎!」

想得太美了!我們今天的目標還沒達成呢!

接下來我們會製作 html 畫面

製作首頁與登入頁

寫完上面的第一支程式之後,想必大家對網頁開發有一定的感覺了,因此我們就順著這個感覺走,在製作首頁與登入頁的部分就是兩個方向

  1. 在 request handler 中回傳 html 畫面
  2. routing 的部分加入 /index 並綁定 request handler

在 request handler 中回傳 html 畫面

要回傳 html 還不簡單,只要在 response 中寫入 html 就可以了吧!

修改上面的範例如下

func test(w http.ResponseWriter, r *http.Request) {
	str := `<!DOCTYPE html>
<html>
<head><title>首頁</title></head>
<body><h1>首頁</h1><p>我的第一個首頁</p></body>
</html>
`
	w.Write([]byte(str))
}

重新運行 go run main.go 後存取 http://127.0.0.1:8888 會出現以下的畫面

是的沒錯,這樣的確是成功的顯示 html 的網頁,但每次要修改 html 的時候就要重新修改程式,html 的內容也不能是動態的!

因此,我們可以使用 html/template package 來解決這個問題!

template

首先,我們先定義好 template 內所要的參數

type IndexData struct {
	Title   string
	Content string
}

我們可以在 import 的部分將 html/template 進行導入


import (
	"log"
    "html/template"
	"net/http"
)

之後將原先的 html 內容抽出來,建立與放入與 main.go 同一個目錄底下名為 index.html 的檔案,{{}} 內的參數對應到上方 IndexData 結構內的變數

<!DOCTYPE html>
<html>
<head><title>{{.Title}}</title></head>
<body><h1>{{.Title}}</h1><p>{{.Content}}</p></body>
</html>

最後我們就可以修改原先的 request handler 如下

func test(w http.ResponseWriter, r *http.Request) {
	tmpl := template.Must(template.ParseFiles("./index.html"))
	data := new(IndexData)
	data.Title = "首頁"
	data.Content = "我的第一個首頁"
	tmpl.Execute(w, data)
}

完成後程式碼如下

package main

import (
	"html/template"
	"log"
	"net/http"
)

type IndexData struct {
	Title   string
	Content string
}

func test(w http.ResponseWriter, r *http.Request) {
	tmpl := template.Must(template.ParseFiles("./index.html"))
	data := new(IndexData)
	data.Title = "首頁"
	data.Content = "我的第一個首頁"
	tmpl.Execute(w, data)
}
func main() {
	http.HandleFunc("/", test)
	err := http.ListenAndServe(":8888", nil)
	if err != nil {
		log.Fatal("ListenAndServe: ", err)
	}
}

重新運行 go run main.go 後存取 http://127.0.0.1:8888 的畫面與上方相同,但這樣的寫法更為動態,不但讓我們可以單獨維護 html 檔案,也可以在 html 內動態放入程式產出的資料,這個設計真的讚讚。

routing 的部分加入 /index 並綁定相關的 request handler

這一步就相對簡單很多了,基本上就是再實作一次 HandleFunc 方法,將 /indextest 方法進行綁定即可

http.HandleFunc("/index", test)

最終程式如下

package main

import (
	"html/template"
	"log"
	"net/http"
)

type IndexData struct {
	Title   string
	Content string
}

func test(w http.ResponseWriter, r *http.Request) {
	tmpl := template.Must(template.ParseFiles("./index.html"))
	data := new(IndexData)
	data.Title = "首頁"
	data.Content = "我的第一個首頁"
	tmpl.Execute(w, data)
}
func main() {
	http.HandleFunc("/", test)
	http.HandleFunc("/index", test)
	err := http.ListenAndServe(":8888", nil)
	if err != nil {
		log.Fatal("ListenAndServe: ", err)
	}
}

小結

今天主要就是透過 golang 內建的 package 建立基本的網頁,大家可以發現 golang 非常適合寫網頁,整體編譯與回應的速度都令人滿意,最重要的是不用關聯到外來的 package 這件事情就贏了!

雖然原生已經有提供相關的 package,但要建立一個完整的網站需要考慮更多的情況,例如 middlewareresponse 的格式request 資料驗證 等等,這些東西當然我們可以自己處理,但是如果有現成幫我們做好的 framework 就更好了,因此,明天就讓我們來介紹 gin 這個 web framework 吧!

參考


上一篇
Day3 | Web 基礎原理介紹
下一篇
Day5 | Gin - 好用的 web framework
系列文
Go into Web!30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

2 則留言

0
eric19740521
iT邦新手 1 級 ‧ 2021-02-21 21:43:47

你好,

我按照你的方式做
routing 的部分加入底下三個

http.HandleFunc("/", test)
http.HandleFunc("/index", test)
http.HandleFunc("/p1", p1)

跟一個p1函數

func p1(w http.ResponseWriter, r *http.Request) {

	currentTime:=time.Now()  
	fmt.Println("p1============")
	fmt.Println(currentTime)
	
	tmpl := template.Must(template.ParseFiles("./p1.html"))
	data := new(IndexData)
	data.Title = "頁1"
	data.Content = "我的第一頁"
	tmpl.Execute(w, data)
}

不知道是否.我理解錯誤???
瀏覽器上輸入 http://127.0.0.1:8888/ ,應該是執行test函數
瀏覽器上輸入 http://127.0.0.1:8888/index ,應該是執行test函數
瀏覽器上輸入 http://127.0.0.1:8888/p1 ,應該是執行p1函數

可是 瀏覽器上輸入 http://127.0.0.1:8888/p1,執行test及p1函數

瀏覽器上輸入 http://127.0.0.1:8888/p1

https://ithelp.ithome.com.tw/upload/images/20210221/200132946Bh6Tuf6AU.png

不知道為何會多了,index============
終端機顯示
https://ithelp.ithome.com.tw/upload/images/20210221/20013294pQQv78syBU.png

main.go ,完整程式碼


package main

import (
	"time"
    "fmt"	
	"html/template"
	"log"
	"net/http"
)

type IndexData struct {
	Title   string
	Content string
}

func test(w http.ResponseWriter, r *http.Request) {

	currentTime:=time.Now()  
	fmt.Println("index============")
	fmt.Println(currentTime)

	tmpl := template.Must(template.ParseFiles("./index.html"))
	data := new(IndexData)
	data.Title = "首頁"
	data.Content = "我的第一個首頁"
	tmpl.Execute(w, data)
}
func p1(w http.ResponseWriter, r *http.Request) {

	currentTime:=time.Now()  
	fmt.Println("p1============")
	fmt.Println(currentTime)
	
	tmpl := template.Must(template.ParseFiles("./p1.html"))
	data := new(IndexData)
	data.Title = "頁1"
	data.Content = "我的第一頁"
	tmpl.Execute(w, data)
}
func main() {
	http.HandleFunc("/", test)
	http.HandleFunc("/index", test)
	http.HandleFunc("/p1", p1)
	err := http.ListenAndServe(":8888", nil)
	if err != nil {
		log.Fatal("ListenAndServe: ", err)
	}
}

index.html

<!DOCTYPE html>
<html>
<head><title>{{.Title}}</title></head>
<body><h1>{{.Title}}</h1><p>{{.Content}}</p></body>
</html>

p1.html


<!DOCTYPE html>
<html>
<head><title>{{.Title}}</title></head>
<body><h1>{{.Title}}</h1><p>{{.Content}}</p></body>
</html>
ray_wu iT邦新手 5 級 ‧ 2022-07-27 14:17:46 檢舉

不知道你是否解決了問題,但我看到後覺得有趣所以研究了一下。
如果使用terminal curl http://127.0.0.1:8888 的話就會正常的只出現一次test, 所以排除net/http本身的功能問題。
google後發現一篇文章
https://studygolang.com/topics/5126
(註:是大陸的網站,如果擔心可以不點擊
裡面有人提到如果你是使用chrome瀏覽器的話,chrome默認會請求favicon.ico,所以main.go中func test會被執行兩次,其中一次就是chrome請求favicon.ico

謝謝..難怪會發生 取兩次的問題

0
zhijiun
iT邦新手 4 級 ‧ 2022-10-04 22:21:20

想請問,為何我的 localhost:8080/ 會出現 404 found 顯示找不到伺服器

這是我的程式碼

https://ithelp.ithome.com.tw/upload/images/20221005/20148157dJUONLWtel.png

我要留言

立即登入留言